﻿using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace RailYardMarker
{
    public class MainFormViewModel: ViewModelBase
    {
        private Yard _yard;      //Root yard
        private Yard _curYard;   //Current yard
        private string _path = string.Empty;

        public MainFormViewModel()
        {
            AddNewPointCommand = new RelayCommand<ValueTuple<double, double>>(
                (cor) =>
                {
                    YPoint p = new YPoint()
                    {
                        Id = Yard.MaxPointId(_yard) + 1,
                        Latitude = cor.Item1,
                        Longitude = cor.Item2,
                    };
                    _curYard.Points.Add(p);
                },
                (cor) =>
                {
                    return _curYard != null;
                });
            AddBoundaryPointCommand = new RelayCommand<ValueTuple<double, double>>(
                (cor) =>
                {
                    BoundaryPoint bpoint = new BoundaryPoint()
                    {
                        Id = Yard.MaxBoundaryPointId(_yard) + 1,
                        Latitude = cor.Item1,
                        Longitude = cor.Item2,
                    };
                    _curYard.Boundary.Add(bpoint);
                },
                (cor) =>
                {
                    return _curYard != null ;
                });
            AddConnectionCommand = new RelayCommand(
               () =>
               {
                   _curYard.Connections.Add(new Connection() { Id = Yard.MaxConnectionId(_yard) + 1 });
               },
               () =>
               {
                   return _curYard != null;
               });
            AddNewYardCommand = new RelayCommand<Yard>(
                (y) =>
                {
                    Yard yard = new Yard();
                    yard.Info.Id = Guid.NewGuid().ToString();
                    yard.Info.Name = "New Yard";
                    y.Yards.Add(yard);
                    BindRootYard(yard);
                },
                (y) =>
                {
                    return y != null;
                });
        }

        public string CurYardName
        {
            get
            {
                if(CurYard != null)
                {
                    return CurYard.Info.Name;
                }
                else
                {
                    return "Unselected";
                }
            }
        }
        public string Path
        {
            get => _path;
            set
            {
                if (Set(ref _path, value))
                {

                }
            }
        }
        public Yard Yard 
        { 
            get => _yard;
            set
            {
                if(Set(ref _yard, value))
                {
                    OnYardLoaded?.Invoke(this, null);
                }
            }
        }
        public Yard CurYard
        {
            get => _curYard;
            set
            {
                if (Set(ref _curYard, value))
                {
                    OnCurrentYardChanged?.Invoke(this, null);
                    RaisePropertyChanged(nameof(CurYardName));
                }
            }
        }

        public void CreateNewYard()
        {
            Yard = new Yard();
            CurYard = Yard;
            BindRootYard(Yard);
        }
        public void ReadYardFromYaml(string path)
        {
            using (StreamReader reader = new StreamReader(path))
            {
                string fileContent = reader.ReadToEnd();
                // 处理文件内容
                var deserializer = new DeserializerBuilder()
                    .WithNamingConvention(UnderscoredNamingConvention.Instance)
                    .Build();

                Yard = deserializer.Deserialize<Yard>(fileContent);
                BindRootYard(Yard);
                CurYard = Yard;
            }
        }
        public void WriteYardToYaml()
        {
            WriteYardToYaml(Path);
        }
        public void WriteYardToYaml(string path)
        {
            var serializer = new SerializerBuilder()
               .WithNamingConvention(UnderscoredNamingConvention.Instance)
               .Build();
            var yaml = serializer.Serialize(Yard);
            File.WriteAllText(path, yaml);
            Path = path;
        }

        private void BindRootYard(Yard y)
        {
            y.SetEventHandler();
            y.OnNameChanged += Y_OnInfoChanged;
            y.OnIdChanged += Y_OnInfoChanged;
            y.OnPointAdded += Y_OnPointAdded;
            y.OnPointChanged += Y_OnPointChanged;
            y.OnPointDeleted += Y_OnPointDeleted;
            y.OnConnectionAdded += Y_OnConnectionAdded;
            y.OnConnectionChanged += Y_OnConnectionChanged;
            y.OnConnectionDeleted += Y_OnConnectionDeleted;
            y.OnBoundaryPointAdded += Y_OnBoundaryPointAdded;
            y.OnBoundaryPointChanged += Y_OnBoundaryPointChanged;
            y.OnBoundaryPointDeleted += Y_OnBoundaryPointDeleted;
            y.OnSubYardAdded += Y_OnSubYardAdded;
            y.OnSubYardDeleted += Y_OnSubYardDeleted;
            foreach (Yard sub in y.Yards)
            {
                BindRootYard(sub);
            }
        }
        private void UnbindRootYard(Yard y)
        {
            y.OnNameChanged -= Y_OnInfoChanged;
            y.OnIdChanged -= Y_OnInfoChanged;
            y.OnPointAdded -= Y_OnPointAdded;
            y.OnPointChanged -= Y_OnPointChanged;
            y.OnPointDeleted -= Y_OnPointDeleted;
            y.OnConnectionAdded -= Y_OnConnectionAdded;
            y.OnConnectionChanged -= Y_OnConnectionChanged;
            y.OnConnectionDeleted -= Y_OnConnectionDeleted;
            y.OnBoundaryPointAdded -= Y_OnBoundaryPointAdded;
            y.OnBoundaryPointChanged -= Y_OnBoundaryPointChanged;
            y.OnBoundaryPointDeleted -= Y_OnBoundaryPointDeleted;
            y.OnSubYardAdded -= Y_OnSubYardAdded;
            y.OnSubYardDeleted -= Y_OnSubYardDeleted;
            foreach (Yard sub in y.Yards)
            {
                UnbindRootYard(sub);
            }
        }

        #region Local Events
        private void Y_OnInfoChanged(object sender, YardInfoEventArgs e)
        {
            OnYardInfoChanged?.Invoke(sender, e);
            if(e.Yard == _curYard)
            {
                RaisePropertyChanged(nameof(CurYardName));
            }
        }
        private void Y_OnPointDeleted(object sender, PointEventArgs e)
        {
            OnYardPointDeleted?.Invoke(sender, e);
        }
        private void Y_OnPointChanged(object sender, PointEventArgs e)
        {
            OnYardPointUpdated?.Invoke(sender, e);
        }
        private void Y_OnPointAdded(object sender, PointEventArgs e)
        {
            OnYardPointAdded?.Invoke(sender, e);
        }
        private void Y_OnConnectionAdded(object sender, ConnectionEventArgs e)
        {
            OnYardConnectionAdded?.Invoke(sender, e);
        }
        private void Y_OnConnectionChanged(object sender, ConnectionEventArgs e)
        {
            OnYardConnectionUpdated?.Invoke(sender, e);
        }
        private void Y_OnConnectionDeleted(object sender, ConnectionEventArgs e)
        {
            OnYardConnectionDeleted?.Invoke(sender, e);
        }
        private void Y_OnBoundaryPointAdded(object sender, BoundaryEventArgs e)
        {
            OnYardBoundaryPointAdded?.Invoke(sender, e);
        }
        private void Y_OnBoundaryPointChanged(object sender, BoundaryEventArgs e)
        {
            OnYardBoundaryPointUpdated?.Invoke(sender, e);
        }
        private void Y_OnBoundaryPointDeleted(object sender, BoundaryEventArgs e)
        {
            OnYardBoundaryPointDeleted?.Invoke(sender, e);
        }
        private void Y_OnSubYardAdded(object sender, YardItemEventArgs e)
        {
            OnYardSubYardAddedd?.Invoke(sender, e);
        }
        private void Y_OnSubYardDeleted(object sender, YardItemEventArgs e)
        {
            OnYardSubYardDeleted?.Invoke(sender, e);
        }

        public event EventHandler OnYardLoaded;
        public event EventHandler OnCurrentYardChanged;
        public event EventHandler<YardInfoEventArgs> OnYardInfoChanged;
        public event EventHandler<PointEventArgs> OnYardPointAdded;
        public event EventHandler<PointEventArgs> OnYardPointUpdated;
        public event EventHandler<PointEventArgs> OnYardPointDeleted;
        public event EventHandler<ConnectionEventArgs> OnYardConnectionAdded;
        public event EventHandler<ConnectionEventArgs> OnYardConnectionUpdated;
        public event EventHandler<ConnectionEventArgs> OnYardConnectionDeleted;
        public event EventHandler<BoundaryEventArgs> OnYardBoundaryPointAdded;
        public event EventHandler<BoundaryEventArgs> OnYardBoundaryPointUpdated;
        public event EventHandler<BoundaryEventArgs> OnYardBoundaryPointDeleted;
        public event EventHandler<YardItemEventArgs> OnYardSubYardAddedd;
        public event EventHandler<YardItemEventArgs> OnYardSubYardDeleted;
        #endregion

        #region Commands
        public RelayCommand<ValueTuple<double, double>> AddNewPointCommand { get; private set; }
        public RelayCommand<ValueTuple<double, double>> AddBoundaryPointCommand { get; private set; }
        public RelayCommand AddConnectionCommand { get; private set; }
        public RelayCommand<Yard> AddNewYardCommand { get; private set; }
        #endregion
    }

    //Events
    public class YardInfoEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
    }
    public class PointEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public YPoint Point { get; set; }
        public int Index { get; set; }
    }
    public class ConnectionEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public Connection Connection { get; set; }
        public int Index { get; set; }
    }
    public class BoundaryEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public BoundaryPoint Boundary { get; set; }
        public int Index { get; set; }
    }
    public class YardItemEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public Yard SubYard { get; set; }
        public int Index { get; set; }
    }

    //View Model Objects
    public class Yard : ObservableObject
    {
        public YardInfo Info { get; set; } = new YardInfo();
        public BindingList<YPoint> Points { get; set; } = new BindingList<YPoint>();
        public BindingList<Yard> Yards { get; set; } = new BindingList<Yard>();
        public BindingList<Connection> Connections { get; set; } = new BindingList<Connection>();
        public BindingList<BoundaryPoint> Boundary { get; set; } = new BindingList<BoundaryPoint>();

        public YPoint GetPointById(int id)
        {
            var p = this.Points.FirstOrDefault(i => i.Id == id);
            if (p == null)
            {
                foreach (Yard sub in this.Yards)
                {
                    p = sub.GetPointById(id);
                    if (p != null) break;
                }
            }
            return p;
        }
        public static int MaxPointId(Yard yard)
        {
            int cur = 0;
            foreach (var p in yard.Points)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxPointId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }
        public static int MaxBoundaryPointId(Yard yard)
        {
            int cur = 100;
            foreach (var p in yard.Boundary)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxBoundaryPointId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }
        public static int MaxConnectionId(Yard yard)
        {
            int cur = 10000;
            foreach (var p in yard.Connections)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxConnectionId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }

        public void SetEventHandler()
        {
            this.Info.PropertyChanged += Info_PropertyChanged;
            this.Points.ListChanged += Points_ListChanged;
            this.Connections.ListChanged += Connections_ListChanged;
            this.Boundary.ListChanged += Boundary_ListChanged;
            this.Yards.ListChanged += Yards_ListChanged;
        }

        private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            YardInfoEventArgs args = new YardInfoEventArgs();
            args.Yard = this;
            if (e.PropertyName == nameof(YardInfo.Name))
            {
                OnNameChanged?.Invoke(this, args);
            }
            else if (e.PropertyName == nameof(YardInfo.Id))
            {
                OnIdChanged?.Invoke(this, args);
            }
        }
        private void Points_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Point = Points[e.NewIndex];
                OnPointAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Point = Points[e.NewIndex];
                OnPointChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnPointDeleted?.Invoke(sender, args);
            }
        }
        private void Connections_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Connection = Connections[e.NewIndex];
                OnConnectionAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Connection = Connections[e.NewIndex];
                OnConnectionChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnConnectionDeleted?.Invoke(sender, args);
            }
        }
        private void Boundary_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Boundary = Boundary[e.NewIndex];
                OnBoundaryPointAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Boundary = Boundary[e.NewIndex];
                OnBoundaryPointChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnBoundaryPointDeleted?.Invoke(sender, args);
            }
        }
        private void Yards_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                YardItemEventArgs args = new YardItemEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.SubYard = Yards[e.NewIndex];
                OnSubYardAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                YardItemEventArgs args = new YardItemEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnSubYardDeleted?.Invoke(sender, args);
            }
        }


        public event EventHandler<YardInfoEventArgs> OnNameChanged;
        public event EventHandler<YardInfoEventArgs> OnIdChanged;
        public event EventHandler<PointEventArgs> OnPointAdded;
        public event EventHandler<PointEventArgs> OnPointDeleted;
        public event EventHandler<PointEventArgs> OnPointChanged;
        public event EventHandler<ConnectionEventArgs> OnConnectionAdded;
        public event EventHandler<ConnectionEventArgs> OnConnectionDeleted;
        public event EventHandler<ConnectionEventArgs> OnConnectionChanged;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointAdded;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointDeleted;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointChanged;
        public event EventHandler<YardItemEventArgs> OnSubYardAdded;
        public event EventHandler<YardItemEventArgs> OnSubYardDeleted;
    }
    public class BoundaryPoint : ObservableObject
    {
        private int id;
        private double latitude;
        private double longitude;

        public int Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public double Latitude
        {
            get => latitude;
            set => Set(ref latitude, value);
        }
        public double Longitude
        {
            get => longitude;
            set => Set(ref longitude, value);
        }

        public override string ToString()
        {
            return $"Point_{Id}";
        }
    }
    public class YardInfo : ObservableObject
    {
        private string id;
        private string name;
        private string state;
        private string bts_yard_id;
        private int avgCarsPerYard;
        private int numOfTrack;

        public string Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public string Name
        {
            get => name;
            set => Set(ref name, value);
        }
        public string State
        {
            get => state;
            set => Set(ref state, value);
        }
        public string Bts_yard_id
        {
            get => bts_yard_id;
            set => Set(ref bts_yard_id, value);
        }
        public int AvgCarsPerYard
        {
            get => avgCarsPerYard;
            set => Set(ref avgCarsPerYard, value);
        }
        public int NumOfTrack
        {
            get => numOfTrack;
            set => Set(ref numOfTrack, value);
        }
    }
    public class YPoint : ObservableObject
    {
        private int _Id;
        private YPointType _Type;
        private double _Latitude;
        private double _Longitude;

        public int Id
        {
            get => _Id;
            set => Set(ref _Id, value);
        }
        public YPointType Type
        {
            get => _Type;
            set => Set(ref _Type, value);
        }
        public double Latitude
        {
            get => _Latitude;
            set => Set(ref _Latitude, value);
        }
        public double Longitude
        {
            get => _Longitude;
            set => Set(ref _Longitude, value);
        }

        public override string ToString()
        {
            return $"Point_{Id}: {Type.ToString()}";
        }
    }
    public class Connection : ObservableObject 
    {
        private int id;
        private YConnectionType type;
        private int fromPointId;
        private int toPointId;

        public int Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public YConnectionType Type
        {
            get => type;
            set => Set(ref type, value);
        }
        public int FromPointId
        {
            get => fromPointId;
            set => Set(ref fromPointId, value);
        }
        public int ToPointId
        {
            get => toPointId;
            set => Set(ref toPointId, value);
        }

        public override string ToString()
        {
            return $"Connect_{Id}: {Type.ToString()}";
        }
    }

    //Enums
    public enum YConnectionType
    {
        bridge,
        inbound_line
    }
    public enum YPointType
    {
        gate_in,
        gate_out,
        inbound_point,
        outbound_point,
        hump
    }
}
